Interactive maps on Leaflet¶
Whenever you go into a website that has some kind of interactive map, it is quite probable that you are wittnessing a map that has been made with a JavaScipt library called Leaflet (the other popular one that you might have wittnessed is called OpenLayers.
There is also a Python module called Folium that makes it possible visualize data that’s been manipulated in Python on an interactive Leaflet map.
Creating a simple interactive web-map¶
Let’s first see how we can do a simple interactive web-map without any data on it. We just visualize OpenStreetMap on a specific location of the a world.
- First thing that we need to do is to create a Map instance. There are few parameters that we can use to adjust how in our Map instance that will affect how the background map will look like.
- See documentation of class folium.folium.Map() for all avaiable options.
In [91]:
import folium
# Create a Map instance
m = folium.Map(location=[60.25, 24.8],
zoom_start=10, control_scale=True)
The first parameter location takes a pair of lat, lon values as list
as an input which will determine where the map will be positioned when
user opens up the map. zoom_start -parameter adjusts the default
zoom-level for the map (the higher the number the closer the zoom is).
control_scale defines if map should have a scalebar or not.
- Let’s see what our map looks like:
In [92]:
m
Out[92]:
- We can also save the map already now
- Let’s save the map as a html file
base_map.html:
In [93]:
outfp = "base_map.html"
m.save(outfp)
Navigate to the location where you saved the html file and open it in a web browser (preferably Google Chrome) to see the output.
- Let’s change the basemap style to
Stamen Tonerand change the location of our map slightly. Thetiles-parameter is used for changing the background map provider and map style (see the documentation for all possible ones).
In [94]:
# Let's change the basemap style to 'Stamen Toner'
m = folium.Map(location=[40.730610, -73.935242], tiles='Stamen Toner',
zoom_start=12, control_scale=True, prefer_canvas=True)
m
Out[94]:
- let’s also save this map as a html file:
In [95]:
# Filepath to the output
outfp = "base_map2.html"
# Save the map
m.save(outfp)
Task¶
Let’s take a few moments and play around with the parameters. Save the map and see how those changes affect the look of the map.
Adding layers to the map¶
Adding layers to a web-map is fairly straightforward and similar procedure as with Bokeh and we can use familiar tools to handle the data, i.e. Geopandas. Our ultimate aim is to create a plot like this where population in Helsinki and the address points are plotted on top of a web-map:
Let’s first practice by adding the address points onto the Helsinki basemap: - read input points using Geopandas:
In [96]:
import geopandas as gpd
# File path
points_fp = r"dataE5/addresses.shp"
# Read the data
points = gpd.read_file(points_fp)
#Check input data
points.head()
Out[96]:
| address | id | geometry | |
|---|---|---|---|
| 0 | Kampinkuja 1, 00100 Helsinki, Finland | 1001 | POINT (24.9301701 60.1683731) |
| 1 | Kaivokatu 8, 00101 Helsinki, Finland | 1002 | POINT (24.9418933 60.1698665) |
| 2 | Hermanstads strandsväg 1, 00580 Helsingfors, F... | 1003 | POINT (24.9774004 60.18735880000001) |
| 3 | Itäväylä, 00900 Helsinki, Finland | 1004 | POINT (25.0919641 60.21448089999999) |
| 4 | Tyynenmerenkatu 9, 00220 Helsinki, Finland | 1005 | POINT (24.9214846 60.1565781) |
In [99]:
# Convert points to GeoJson
#points_gjson = folium.features.GeoJson(points.to_json())
points_gjson = folium.features.GeoJson(points)
Now we have our population data stored as GeoJSON format which basically
contains the data as text in a similar way that it would be written in
the .geojson -file.
- add the points onto the Helsinki basemap
In [100]:
# Create a Map instance
m = folium.Map(location=[60.25, 24.8], tiles = 'cartodbpositron', zoom_start=11, control_scale=True)
# Add points to the map instance
points_gjson.add_to(m)
# Alternative syntax for adding points to the map instance
#m.add_child(points_gjson)
#Show map
m
Out[100]:
Heat map¶
- folium.plugins.HeatMap requires a list of points, or a numpy array as input
In [101]:
# Get lat and lon coordinates
points['lon'] = points["geometry"].x
points['lat'] = points["geometry"].y
# Conver lat and lon to numpy array (old method: .as_matrix())
points_array = points[['lat', 'lon']].values
In [102]:
points.head()
Out[102]:
| address | id | geometry | lon | lat | |
|---|---|---|---|---|---|
| 0 | Kampinkuja 1, 00100 Helsinki, Finland | 1001 | POINT (24.9301701 60.1683731) | 24.930170 | 60.168373 |
| 1 | Kaivokatu 8, 00101 Helsinki, Finland | 1002 | POINT (24.9418933 60.1698665) | 24.941893 | 60.169866 |
| 2 | Hermanstads strandsväg 1, 00580 Helsingfors, F... | 1003 | POINT (24.9774004 60.18735880000001) | 24.977400 | 60.187359 |
| 3 | Itäväylä, 00900 Helsinki, Finland | 1004 | POINT (25.0919641 60.21448089999999) | 25.091964 | 60.214481 |
| 4 | Tyynenmerenkatu 9, 00220 Helsinki, Finland | 1005 | POINT (24.9214846 60.1565781) | 24.921485 | 60.156578 |
In [103]:
from folium.plugins import HeatMap
# Create a Map instance
m = folium.Map(location=[60.25, 24.8], tiles = 'stamentoner', zoom_start=11, control_scale=True)
# Add heatmap to map instance
# Available parameters: HeatMap(data, name=None, min_opacity=0.5, max_zoom=18, max_val=1.0, radius=25, blur=15, gradient=None, overlay=True, control=True, show=True)
HeatMap(points_array).add_to(m)
# Alternative syntax:
#m.add_child(HeatMap(points_array, radius=15))
# Show map
m
Out[103]:
Choroplet map¶
Next, let’s check how we can overlay a population map on top of a basemap using folium’s choropleth method. This method is able to read the geometries and attributes directly from a geodataframe. This example is modified from the Folium quicksart.
- First, read in the population grid:
In [104]:
# Filepaths
fp = "dataE5/Vaestotietoruudukko_2015.shp"
# Read Data
data = gpd.read_file(fp)
# Check the data
data.head()
Out[104]:
| INDEX | ASUKKAITA | ASVALJYYS | IKA0_9 | IKA10_19 | IKA20_29 | IKA30_39 | IKA40_49 | IKA50_59 | IKA60_69 | IKA70_79 | IKA_YLI80 | geometry | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 688 | 8 | 31.0 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | POLYGON ((25472499.99532626 6689749.005069185,... |
| 1 | 703 | 6 | 42.0 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | POLYGON ((25472499.99532626 6685998.998064222,... |
| 2 | 710 | 8 | 44.0 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | POLYGON ((25472499.99532626 6684249.004130407,... |
| 3 | 711 | 7 | 64.0 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | POLYGON ((25472499.99532626 6683999.004997005,... |
| 4 | 715 | 19 | 23.0 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 | POLYGON ((25472499.99532626 6682998.998461431,... |
- re-project layer into WGS 84 (epsg: 4326)
- Modify columns:
In [105]:
# Re-project to WGS84
data = data.to_crs(epsg=4326)
# Check layer crs definition
print(data.crs)
# Make a selection (only data above 0 and below 1000)
data = data.loc[(data['ASUKKAITA'] > 0) & (data['ASUKKAITA'] <= 1000)]
# Create a Geo-id which is needed by the Folium (it needs to have a unique identifier for each row)
data['geoid'] = data.index.astype(str)
# Select only needed columns
data = data[['geoid', 'ASUKKAITA', 'geometry']]
# Convert to geojson (not needed for the simple coropleth map!)
#pop_json = data.to_json()
#check data
data.head()
{'init': 'epsg:4326', 'no_defs': True}
Out[105]:
| geoid | ASUKKAITA | geometry | |
|---|---|---|---|
| 0 | 0 | 8 | POLYGON ((24.50236241370834 60.31927864851716,... |
| 1 | 1 | 6 | POLYGON ((24.50287385337343 60.28562263749414,... |
| 2 | 2 | 8 | POLYGON ((24.50311210582754 60.26991652312412,... |
| 3 | 3 | 7 | POLYGON ((24.50314612020954 60.26767278939882,... |
| 4 | 4 | 19 | POLYGON ((24.50328212493893 60.25869775697638,... |
In [106]:
# Create a Map instance
m = folium.Map(location=[60.25, 24.8], tiles = 'cartodbpositron', zoom_start=10, control_scale=True)
# Plot a choropleth map
# Notice: 'geoid' column that we created earlier needs to be assigned always as the first column
m.choropleth(
geo_data=data,
name='choropleth',
data=data,
columns=['geoid', 'ASUKKAITA'],
key_on='feature.id',
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
line_color='white',
line_weight=0,
highlight=False,
smooth_factor=1.0,
#threshold_scale=[100, 250, 500, 1000, 2000],
legend_name= 'Population in Helsinki')
#Show map
m
Out[106]:
Clustered point map¶
Let’s visualize the address points (locations of transport stations in Helsinki) on top of the choropleth map using clustered markers.
In [107]:
from folium.plugins import MarkerCluster
# Create a Clustered map where points are clustered
marker_cluster = MarkerCluster().add_to(m)
In [108]:
# Create clustered points on top of the map
for idx, row in points.iterrows():
# Get lat and lon of points
lon = row['geometry'].x
lat = row['geometry'].y
# Get address information
address = row['address']
# Add marker to the map
folium.RegularPolygonMarker(location=[lat, lon], popup=address, fill_color='#2b8cbe', number_of_sides=6, radius=8).add_to(marker_cluster)
In [109]:
#Show map:
m
Out[109]:
In [ ]: